package des.steganografie;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.WritableRaster;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;

public class Steganography
{
    //Lege constructor
    public Steganography()
            {
            }

    //Image encoderen met tekst
    public boolean encode(String path, String original, String ext1, String stegan, String message) throws Exception
       {
            String file_name = image_path(path,original,ext1);
            BufferedImage image_orig = getImage(file_name);

            //User space is niet nodig voor encrypting
            BufferedImage image = user_space(image_orig);
            image = add_text(image,message);

            return(setImage(image, new File(image_path(path,stegan,"png")),"png"));
       }


    //Image decoderen
    //Image moet .png zijn
    public String decode(String path, String name)
    {
        byte[]decode;
        try
        {
            //User space is nodig voor decrypting
            BufferedImage image =
                    user_space(getImage(image_path(path,name,"png")));
            decode = decode_text(get_byte_data(image));
            return(new String(decode));
        }
        catch(Exception e)
        {
            JOptionPane.showMessageDialog(null,"No hidden message","Error",JOptionPane.ERROR_MESSAGE);
            return "";
        }
    }

    //Retourneert path van de file
    private String image_path(String path, String name, String ext)
    {
        return path + "/" + name + "." + ext;
    }

    //retourneeert image file
    private BufferedImage getImage(String f)
    {
        BufferedImage image = null;
        File file = new File(f);
        try
        {
            image = ImageIO.read(file);
        }
        catch(Exception ex)
        {
            JOptionPane.showMessageDialog(null,"Image could not be read","Error",JOptionPane.ERROR_MESSAGE);
        }
        return image;
    }

    //Methode om een image op te slaan
    private boolean setImage(BufferedImage image, File file, String ext)
    {
        try
        {
            file.delete();  //Delete resources used by the file
            ImageIO.write(image, ext, file);
            return true;
        }
        catch(Exception e)
        {
            JOptionPane.showMessageDialog(null,"File could not be saved!","Error",JOptionPane.ERROR_MESSAGE);
            return false;
        }
    }


    //Toevoegen van tekst aan bestand
    private BufferedImage add_text(BufferedImage image, String text) throws Exception
    {
        //Converteer alles naar byte arrays
        byte img[] = get_byte_data(image);
        byte msg[] = text.getBytes();
        byte len[] = bit_conversion(msg.length);
        try
        {
            encode_text(img,len,0); //Eerste positie
            encode_text(img,msg,32);    //4 bits voor lengte: 4bytes * 8 bits = 3Z bits
        }
        catch(Exception e)
        {
            throw new Exception("Target file cannot hold message");
        }
        return image;
    }


    //Maakt een user space van een buffered image
    private BufferedImage user_space(BufferedImage image)
    {
        //Maak new_img met de attributen van gekozen afbeelding
        BufferedImage new_img =
                new BufferedImage(image.getWidth(), image.getHeight(),
                                  BufferedImage.TYPE_3BYTE_BGR);
        Graphics2D graphics = new_img.createGraphics();
        graphics.drawRenderedImage(image, null);
        graphics.dispose();     //Release memory
        return new_img;
    }

    //Bekom de byte format van een afbeelding
    private byte [] get_byte_data(BufferedImage image )
    {
        WritableRaster raster = image.getRaster();
        DataBufferByte buffer = (DataBufferByte)raster.getDataBuffer();
        return buffer.getData();
    }


    //Genereert buffer formaat van een integer
    private byte[]bit_conversion(int i)
    {
       //only using 4 bytes
        byte byte3 = (byte)((i & 0xFF000000) >>> 24); //0
        byte byte2 = (byte)((i & 0x00FF0000) >>> 16); //0
        byte byte1 = (byte)((i & 0x0000FF00) >>> 8 ); //0
        byte byte0 = (byte)((i & 0x000000FF));

        //{0,0,0,byte0} is equivalent, since all shifts >=8 will be 0
        return(new byte[]{byte3,byte2,byte1,byte0});
    }

    //Encodeer een array van bytes naar een andere array van bytes
    private byte []encode_text(byte[]image, byte[]addition,int offset)
    {
        //check of de data + offset in de image past
        if(addition.length + offset > image.length)
        {
            throw new IllegalArgumentException("File not long enough");
        }

        //Loop door elke addtion byte
        for(int i = 0; i < addition.length;++i)
        {
            int add = addition[i];

            //Loop door de 8 bits van elke byte
            for(int bit = 7; bit >= 0; --bit, ++offset)
            {
                //Shifts [Put comments here]
                int b = (add >>> bit) & 1;
                image[offset] = (byte)((image[offset] & 0xFe | b));
            }
        }
        return image;
    }


    //Haal tekst uit image
    private byte[]decode_text(byte[]image)
    {
        int length = 0;
        int offset = 32;

        //Loop door 32 bytes om tekstlengte te bepalen
        for(int i = 0; i < 32; ++i)
        {
            length = (length << 1) | (image[i] & 1);
        }

        byte[]result = new byte[length];

        //Loop door elke byte van tekst
        for (int b = 0; b < result.length; ++b)
        {
            //Loop door elke bit doorheen elke byte tekst
            for(int i = 0; i < 8; ++i,++offset)
            {
                //Assign bit: [(new byte value) << 1] OR [(text byte) AND 1]
                result[b] = (byte)((result[b] << 1) | (image [offset] & 1));
            }
        }
        return result;
    }
}